/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.commands.connection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;

import com.cea.papyrus.diagraminterchange2.di2.GraphConnector;

/**
 * A lightweight command to move all the anchors of one edge segment.
 * This move the anchors, and edge attached to the anchors.
 * 
 * @author dumoulin
 */
public class MoveEdgeSegmentAnchorsCommand {

	/**
	 * Segment first point.
	 */
	Point source;
	
	/**
	 * segment last point.
	 */
	Point target;
	
	/**
	 * Anchors attached to the segment.
	 */
	List<MoveSegmentAnchorCommand> segmentAnchors;
	
	/**
	 * Create a SegmentAnchors.
	 * Save the anchors belonging to the specified segment.
	 * 
	 * @param p1 
	 * @param p2 
	 * @param anchors 
	 */
	public MoveEdgeSegmentAnchorsCommand(Point p1, Point p2, List<GraphConnector> anchors)
	{
		source = p1;
		target = p2;
		saveAnchors(p1, p2, anchors);
	}
	
	/**
	 * Save the anchors belonging to the specified segment.
	 * 
	 * @param p1 
	 * @param p2 
	 * @param anchors 
	 */
	private void saveAnchors(Point p1, Point p2, List<GraphConnector> anchors)
	{
		segmentAnchors = new ArrayList<MoveSegmentAnchorCommand>();
		Iterator<GraphConnector> iter = anchors.iterator();
		while(iter.hasNext())
		{
			GraphConnector anchor = iter.next();
			if( lineContainsPoint(p1, p2, anchor.getPosition() ) )
			{ // Anchor is on the segment
				segmentAnchors.add(new MoveSegmentAnchorCommand(anchor, p1.getDistance2(anchor.getPosition()), p1.getDistance2(p2) ) );
			}
		}
	}
	
	/**
	 * Restore the saved anchors.
	 */
	public void restoreAnchors()
	{
		Iterator<MoveSegmentAnchorCommand> iter = segmentAnchors.iterator();
		while(iter.hasNext())
		{
			MoveSegmentAnchorCommand savedAnchor = iter.next();
			savedAnchor.restore();
		}
				
	}
	
	/**
	 * Move the source end to the new position.
	 * Recalculate position of anchors on the segment.
	 * 
	 * @param p1 
	 * @param p2 
	 * @param newPos 	 */
	public void moveToSegment(Point p1, Point p2)
	{
		Iterator<MoveSegmentAnchorCommand> iter = segmentAnchors.iterator();
		while(iter.hasNext())
		{
			MoveSegmentAnchorCommand savedAnchor = iter.next();
			savedAnchor.moveToSegment(p1, p2);
		}
		
	}
	
	/**
	 * Move the source end to the new position.
	 * Recalculate position of anchors on the segment.
	 * 
	 * @param newPos 
	 */
	public void moveSourceEnd(Point newPos ) {
		moveToSegment(newPos, target);
	}
	
	/**
	 * Move the source end to the new position.
	 * Recalculate position of anchors on the segment.
	 * 
	 * @param newPos 
	 */
	public void moveTargetEnd(Point newPos)
	{
		moveToSegment(source, newPos);			
	}
	
	/**
	 * Does the specified line contains the specified point ?.
	 * 
	 * @param p1 
	 * @param p 
	 * @param p2 
	 * 
	 * @return 
	 */
	private boolean lineContainsPoint(Point p1, Point p2, Point p) {
		int tolerance = 7;
		Rectangle rect = Rectangle.SINGLETON;
		rect.setSize(0, 0);
		rect.setLocation(p1.x, p1.y);
		rect.union(p2.x, p2.y);
		rect.expand(tolerance, tolerance);
		if (!rect.contains(p.x, p.y))
			return false;

		int v1x, v1y, v2x, v2y;
		int numerator, denominator;
		double result = 0.0;

		if (p1.x != p2.x && p1.y != p2.y) {
			
			v1x = p2.x - p1.x;
			v1y = p2.y - p1.y;
			v2x = p.x - p1.x;
			v2y = p.y - p1.y;
			
			numerator = v2x * v1y - v1x * v2y;
			denominator = v1x * v1x + v1y * v1y;

			result = ((numerator << 10) / denominator * numerator) >> 10;
		}
		
		// if it is the same point, and it passes the bounding box test,
		// the result is always true.
		return result <= tolerance * tolerance;
	}

	/**
	 * Command to move an anchor attached to a command.
	 * This command store the anchor relative position on the segment.
	 * It delegate the position change to {@link MoveAnchorPositionAndAttachedEdgesCommand}.
	 * Move the position proportionally to its old position.
	 * 
	 * @author dumoulin
	 */
  protected class MoveSegmentAnchorCommand {
	  
  	/**
  	 * Command wich will move effectively the anchor and its attached edge.
  	 */
	  MoveAnchorPositionAndAttachedEdgesCommand moveAnchorCommand;
	  
  	/**
  	 * Square distance from source to anchor in old positions.
  	 */
	  double distance2;
	  
  	/**
  	 * Old segment size.
  	 */
	  double oldSegmentSize;
	  
	  /**
  	 * Constructor.
  	 * 
  	 * @param oldSegmentSize  segment old size
  	 * @param distance2 Square distance from segment source to anchor old position.
  	 * @param anchor Anchor to move
  	 */
	  protected MoveSegmentAnchorCommand(GraphConnector anchor, double distance2, double oldSegmentSize)
	  {
		this.distance2 = distance2;
		this.oldSegmentSize = oldSegmentSize;
		moveAnchorCommand = new MoveAnchorPositionAndAttachedEdgesCommand(anchor);
		moveAnchorCommand.savePositions();
	  }
	  
	  /**
  	 * 
  	 */
  	protected void restore()
	  {
		  moveAnchorCommand.restorePositions();
	  }
	  
	  /**
  	 * 
  	 * 
  	 * @param p1 
  	 * @param p2 
  	 */
  	protected void moveToSegment(Point p1, Point p2)
	  {
		  
		  double D2 = p1.getDistance2(p2);
		  double d2 = (distance2/oldSegmentSize)*D2;
		  
		  Point p = new Point(p2.x-p1.x, p2.y-p1.y);
		  
   		  double x = Math.sqrt(d2/D2)*p.x;
   		  double y = Math.sqrt(d2/D2)*p.y;
   		  
   		  Point newPos = new Point(p1.x+x, p1.y+y);
   		  // Move effectively the anchor position, and its associated edge.
   		  moveAnchorCommand.moveAnchor(newPos);
	  }

	/**
	 * 
	 * 
	 * @return the distance2
	 */
	public double getDistance2() {
		return distance2;
	}
	  
  }
	
}
